/**
 * http请求协议解析封装类实现代码.
 * 
 * 开发者   : Marco
 * 创建时间 : 2016年7月10日, 下午5:57
 */

#include "request.h"

#include <map>
#include <stdio.h>

#include <string.h>
#include <stdlib.h>

#ifndef MARCO_NO_HTTP
MARCOPP_NAMESPACE_BEGIN
MARCOPP_NAMESPACE_HTTP_BEGIN

struct urlparam {
    char* name;
    char* value;
};

/**
 * 隐藏的实现类
 */
class request_impl {
public:
    request_impl(method_type method) : path(NULL), params(NULL), param_count(0), method(method){ }
    request_impl(const char* request_path, method_type method)
	: params(NULL), param_count(0), method(method) {
	path = (char*)realloc(path, strlen(request_path)+1); strcpy(path, request_path);
    }
    ~request_impl(void) {
	if (path) free(path);
	if (params) {
	    for (int i = 0; i < param_count; ++i) {
		free(params[i].name);
		free(params[i].value);
	    }
	    free(params);
	}
    }
    
    /*获取与设置请求方法*/
    method_type get_method(void) const { return method; }
    char* get_method_str(char* buf) const {
	switch (method) {
	case method_get: strcpy(buf, "GET"); break;
	case method_post: strcpy(buf, "POST"); break;
	case method_put: strcpy(buf, "PUT"); break;
	case method_delete: strcpy(buf, "DELETE"); break;
	case method_unknow: strcpy(buf, "UNKNOW"); break;
	}
	return buf;
    }
    void set_method(method_type method) { this->method = method; }
    
    /* 获取与设置请求路径 */
    char* get_urlpath(void) { return path; }
    void set_urlpath(const char* urlpath) { path = (char*)realloc(path, strlen(urlpath)+1); strcpy(path, urlpath); }
    void set_urlpath(const char* urlpath, int len) { path = (char*)realloc(path, len+1); strcpy(path, urlpath); }
    
    bool append_urlparam(char* data) {
	return init_url_param(data);
    }
    
    /**
     * 根据url携带参数的名称获取值
     * @param name 名称
     * @return 值或NULL
     */
    char* get_urlparam(const char* name) {
	for (int i = 0; i < param_count; ++i) {
	    if (!strcmp(name, params[i].name)) {
		return params[i].value;
	    }
	}

	return NULL;
    }
    
    int get_urlparam_str(char* buf) {
	int len = 0, ret;
	
	for (int i = 0; i < param_count; ++i) {
	    ret = sprintf(buf+len, "%s=%s&", params[i].name, params[i].value);
	    len += ret;
	}
	
	if (len) {  // 有数据
	    buf[--len] = 0; // 去掉最后一个&
	
	} else {
	    buf[0] = 0;	    // 保证空串
	}
	
	return len;
    }
    
    int get_urlparam_count(void) const {
	return param_count;
    }
    
    /**
     * 初始化url携带的参数, 转化为键值对保存, 会修改原数据内容 name1=value1&name2=value2&.....
     * @param ptr 参数首地址
     * @return 解析成功与失败, 一个参数也没有返回false
     */
    bool init_url_param(char* ptr) {
	char *name, *value;
	char *save_ptr = NULL;
	int capacity = param_count;

	url_decode(ptr);	    // URL解码
//	params = (struct urlparam*)malloc(sizeof(struct urlparam)*capacity);

	while ((name = strtok_r(ptr, "&", &save_ptr))) {
	    if ((value = strchr(name, '='))) {
		*value++ = 0;
//		params.insert(std::pair<string, string>(name, value));
		if (param_count == capacity) {
		    capacity += 10;
		    params = (struct urlparam*)realloc(params, sizeof(struct urlparam)*capacity);
		}
		params[param_count].name = (char*)malloc(value-name);
		strcpy(params[param_count].name, name);
		params[param_count].value = (char*)malloc(save_ptr-value+1); // 前几次都是正常的,最后一次save_ptr正好少1字节,没有\0的位置了
		strcpy(params[param_count].value, value);
		++ param_count; 
	    }

	    ptr = NULL; // 后续循环需要第一个参数是NULL
	}
	
	return (param_count > 0);
    }
    
    /**
     * URL解码, 将URL中的转义字串转化为单字符, 在原缓冲区上修改, 请求数据转换转义字符%7b-->0x7b-->{
     * @param buf (in/out)原字符串, 转化后只会比原串字符更少
     */
    void url_decode(char *buf) {
	int i, count = 0;

	for (i = 0; buf[i]; ++i) {
	    if (buf[i] == '%') { // 转义
		// 输出高4位
		i += 1;
		if (buf[i] >= '0' && buf[i] <= '9') {
		    buf[count] = (buf[i] - '0') << 4;

		} else {
		    buf[count] = (buf[i] - 'A' + 10) << 4;
		}

		// 输出低4位
		i += 1;
		if (buf[i] >= '0' && buf[i] <= '9') {
		    buf[count++] |= buf[i] - '0';

		} else {
		    buf[count++] |= buf[i] - 'A' + 10;
		}

	    } else if (buf[i] == '+') {
		buf[count++] = ' ';

	    } else {
		buf[count++] = buf[i];
	    }
	}

	buf[count] = 0; // 结尾置0
    }

    
    char* path;			    // 请求路径
    struct urlparam* params;	    // url携带参数列表
    int param_count;		    // 参数个数
    method_type method;		    // 请求方法
};



request::request(version_type version, method_type method) : basic(version), impl(new request_impl(method)) { }
request::request(const char* request_path, version_type version , method_type method) 
	    : basic(version), impl(new request_impl(request_path, method)) { }
request::~request(void) { delete impl; }

/**
 * 解析http数据, 数据必须包括整个http协议头, 允许只有部分正文, 解析发现与头中描述的Content-Length不符时, 返回http_parse_again
 * @param data 协议数据原始内容, 解析过程中会修改部分位置, 无论解析成功或失败, 此数据不得再使用.
 * @param data_len (in/out)数据长度, 返回未完成时(http_parse_again), 此变量返回还需多少字节.
 * @return 解析结果, 可能(完成,未完成,失败)
 */
parse_result request::parse(char* data, int* data_len) {
    char *ptr, *urlpath_ptr, *urlparam_ptr, *content_ptr;

    if (!(content_ptr = strstr(data, "\r\n\r\n"))) // 没有完整的http报文头
	return parse_failed;

    /** ========================================
     * Step: 消息报头
     */
    *(content_ptr + 2) = 0; // 多留一个\r\n方便head解析
    content_ptr += 4; // 指向正文

    if ((ptr = strstr(data, "\r\n"))) {
	*ptr = 0;
	ptr += 2; // 指向第一个head头
	if (!init_msghead(ptr)) return parse_failed_head; // 初始化消息报头, 存为键值对
    }

    /** ========================================
     * Step: 取请求方法
     */
    if (!(urlpath_ptr = strchr(data, ' '))) // 查找第一个空格, 取method
	return parse_failed_method;
    *urlpath_ptr++ = 0;	    // 隔开method与url

    if (!strcmp(data, "GET")) {
	impl->set_method(method_get);
    } else if (!strcmp(data, "POST")) {
	impl->set_method(method_post);
    } else if (!strcmp(data, "PUT")) {
	impl->set_method(method_put);
    } else if (!strcmp(data, "DELETE")) {
	impl->set_method(method_delete);
    } else {
	return parse_failed_unknow_method;
    }

    /** ========================================
     * Step: URL处理, path+params  
     */
    if (!(ptr = strchr(urlpath_ptr, ' '))) { // 查找第二个空格, 取URL(包括path与params)
	//	    printf("Can't find path and version space symbol.\n");
	return parse_failed_url;
    }
    *ptr++ = 0; // 隔开url与version, 并指向version首地址

    // 判断是否请求路径中携带了参数params
    if ((urlparam_ptr = strchr(urlpath_ptr, '?'))) { // 请求参数携带了参数
	*urlparam_ptr++ = 0;

	if (!impl->init_url_param(urlparam_ptr))    // 初始化路径携带参数列表
	    return parse_failed_params;
	//	    printf("url params count:%ld\n", params.size());
    }

    impl->set_urlpath(urlpath_ptr);	// 请求路径
    //	printf("url path:%s\n", impl->get_urlpath);

    /** ========================================
     * Step: HTTP版本  
     */
    if (!strcmp(ptr + 5, "1.1")) {
	set_version(version_1_1);
    } else if (!strcmp(ptr + 5, "1.0")) {
	set_version(version_1_0);
    } else {
	return parse_failed_version;
    }

    /** ========================================
     * Step: 非GET方法可能有正文未传送完, 检查是否未处理完
     */
    if (impl->get_method() != method_get) {
	const char* len_ptr = get_msghead("Content-Length");
	int body_len = *data_len - (content_ptr - data);	    // 当前正文长度
	
	if (body_len > 0) { // 有正文内容
	    append_content_str(content_ptr, body_len); // 添加正文
	}

	if (len_ptr) {
	    //		printf("Content-Length:%s, current body len:%d.\n", len_ptr, body_len);
	    int content_len = (int)strtol(len_ptr, NULL, 10); // 正文总长度
	    if (body_len == content_len) return parse_success; // 当前全部读完

	    *data_len = content_len - body_len; // 告诉调用处, 还需要读多少字节
	    if (*data_len < 0) return parse_failed_length; // 不能比当前的还小.
	    return parse_again;

	} else { // head中没有指示长度, 不管当前是否有正文内容, 认为成功
	    return parse_success;
	}
    }

    return parse_success;
}

method_type request::get_method(void) const { return impl->get_method(); }
char* request::get_method_str(char* buf) const { return impl->get_method_str(buf); }
void request::set_method(method_type method) { impl->set_method(method); }

/**
 * 获取与设置请求路径
 */
char* request::get_urlpath(void) { return impl->get_urlpath(); }
void request::set_urlpath(const char* urlpath) { impl->set_urlpath(urlpath); }
void request::set_urlpath(const char* urlpath, int len) { impl->set_urlpath(urlpath, len); }

bool request::append_urlparam(char* data) { return impl->append_urlparam(data); }
char* request::get_urlparam(const char* name) { return impl->get_urlparam(name); }
int request::get_urlparam_str(char* buf) { return impl->get_urlparam_str(buf); }
int request::get_urlparam_count(void) const { return impl->get_urlparam_count(); }

MARCOPP_NAMESPACE_HTTP_END
MARCOPP_NAMESPACE_END
#endif // MARCO_NO_HTTP